Dataset:
We have 3 years of daily sales of Post-its. Those lovable note snippets that hang around your desk.
We will do the following:
Determine which factors influence sales.
Build a model to forecast daily sales.
# Install pacman if needed
if (!require("pacman")) install.packages("pacman")
# load packages
pacman::p_load(pacman,
tidyverse, openxlsx, forecast, modeltime, parsnip, rsample, timetk, broom)
#Import data
postit <- read.xlsx("/Users/anitaowens/Documents/Marketing Analytics-Data Driven Techniques with Excel/Excel Files/Chapter 31 Excel Files/POSTITDATA.xlsx", skipEmptyRows = TRUE)
#Check results
str(postit)
'data.frame': 1096 obs. of 6 variables:
$ Month : num 1 1 1 1 1 1 1 1 1 1 ...
$ Day# : num 1 2 3 4 5 6 7 8 9 10 ...
$ Day : num 40544 40545 40546 40547 40548 ...
$ Price : num 7.52 7.52 5.95 6.2 6.1 6.2 6.98 5.95 7.12 6.98 ...
$ Display? : num 1 0 0 0 0 0 1 0 1 0 ...
$ actualsales: num 390 344 636 483 486 490 524 620 416 464 ...
We have 6 variables:
- Month
- Day number
- Day (This is our date variable but since we imported from Excel we get the Excel formatted dates)
- Pricing for that particular day (7 different prices)
- Display (This is a binary variable. 1 indicates that our product was on a display for that day. 0 indicates that it was not on display).
- Actual sales
#Create New Formatted Date Column
postit$new_date <- as.Date(postit$Day, origin = "1899-12-30")
#Check results
str(postit$new_date)
Date[1:1096], format: "2011-01-01" "2011-01-02" "2011-01-03" "2011-01-04" ...
#Column renaming
postit <- postit %>%
rename(
day_num = 'Day#',
display = 'Display?'
)
#Check results
names(postit)
[1] "Month" "day_num" "Day" "Price"
[5] "display" "actualsales" "new_date"
#Create list of variables that need transformation
fac_vars <- c("Month", "display", "Price")
#Factor Month, Display and Price Variables
postit[,fac_vars] <- lapply(postit[,fac_vars], factor)
#Check results
str(postit)
'data.frame': 1096 obs. of 7 variables:
$ Month : Factor w/ 12 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
$ day_num : num 1 2 3 4 5 6 7 8 9 10 ...
$ Day : num 40544 40545 40546 40547 40548 ...
$ Price : Factor w/ 7 levels "5.95","6.1","6.2",..: 7 7 1 3 2 3 4 1 5 4 ...
$ display : Factor w/ 2 levels "0","1": 2 1 1 1 1 1 2 1 2 1 ...
$ actualsales: num 390 344 636 483 486 490 524 620 416 464 ...
$ new_date : Date, format: "2011-01-01" ...
#Drop Excel formatted Day variable
postit <- postit %>%
select(-Day)
#From timetk package - visualize sales
postit %>%
plot_time_series(new_date, actualsales, .interactive = TRUE)
Our plot of sales indicates that there is a positive trend of sales for our post its.
Trend in time series data is a specific pattern which in which observations are either increasing or decreasing over time. Trends can constantly change.
Task 1: What factors influence sales
#Let's do some exploratory data analysis (EDA)
ggplot(data = postit, aes(x=actualsales)) + geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with
`binwidth`.

#Sales are a bit skewed to the right
ggplot(data=postit, aes(x=log(actualsales))) + geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with
`binwidth`.

#Sales now look more normal after log transformation
ggplot(data = postit, aes(x=Month, y = actualsales)) + geom_boxplot()

#Sales are lowest in March. Sales are generally higher in Q4 and the highest in December. December also appears to have the largest spread. Some outliers during months August and September.
ggplot(data = postit, aes(x=Price, y = actualsales)) + geom_boxplot()

#This is quite interesting, as sales are highest when the price is 5.95. Sales decrease when price increases to 6.1 and so and so forth where the lowest sales happen when the price is at its highest 7.52. There appears to be a relationship between price and sales.
ggplot(data = postit, aes(x=display, y = actualsales)) + geom_boxplot()

#More sales when product is on display.
postit %>%
group_by(display) %>%
summarize(mean_sales = mean(actualsales), median_sales = median(actualsales), sd_sales = sd(actualsales))
#Average sales are 639 vs 587 when no display.
Now we have some idea of the factors that might influence sales, let’s check for significance by using a linear regression model.
# Model Spec
model_spec_lm <- linear_reg() %>%
set_engine("lm")
# Fit Linear Regression Model
model_fit_lm <- model_spec_lm %>%
fit(actualsales ~ Month + Price + display, data = postit)
#Print summary of model in a tidy object
lm_summary <- tidy(model_fit_lm) %>%
mutate(significant = p.value <= 0.05)
lm_summary
#Use the glance function from the broom package to get additional information from the model (e.g. model statitics like r.squared)
glance(model_fit_lm)
A generic interpretation of a linear regression model. For every one unit change in coefficient (term column), moves unit sales by the value of our estimate while all other variables remain at the same level.
Most months are significant but December is positive and the most statistically significant with the highest average sales. March is the month with the lowest average sales. Time of year does impact sales.
All price coefficients are negative and significant which means that all price levels above 5.95 reduces sales and continues to reduce at each price increase. Customers seem to be price sensitive when it comes to post-its. As suggested by our EDA, there is a negative relationship between price and sales.
For display advertising, we can expect an increase of 60 when there is a display.
As for the model statistics, we have a very low p-value and a pretty decent r.square of .885, which tells us that that 89% of the variation in sales is explained by our independent/explanatory variables.
We built this linear model for mostly gaining insights into what factors influence sales.
Task 2: Build a forecast model for sales
We will split our dataset into test and training data. We will use the last 3 months of the dataset as the training set.
#Split data into test and training set
splits <- postit %>%
time_series_split(date_var = new_date, assess = "12 months", cumulative = TRUE)
#Visualize test train split
splits %>%
tk_time_series_cv_plan() %>%
plot_time_series_cv_plan(new_date, actualsales, .interactive = TRUE)
We want to define our model engine. We will be using prophet.
# Model Spec
model_spec_prophet <- prophet_reg() %>%
set_engine("prophet")
# Step 1: Model fit
model_fit_prophet <- model_spec_prophet %>%
fit(actualsales ~ ., data = training(splits))
Disabling daily seasonality. Run prophet with daily.seasonality=TRUE to override this.
model_fit_prophet
parsnip model object
Fit time: 835ms
PROPHET w/ Regressors Model
- growth: 'linear'
- n.changepoints: 25
- changepoint.range: 0.8
- yearly.seasonality: 'auto'
- weekly.seasonality: 'auto'
- daily.seasonality: 'auto'
- seasonality.mode: 'additive'
- changepoint.prior.scale: 0.05
- seasonality.prior.scale: 10
- holidays.prior.scale: 10
- logistic_cap: NULL
- logistic_floor: NULL
- extra_regressors: 19
#Step 2: Put model into a modeltime table
models_tbl <- modeltime_table(model_fit_prophet)
models_tbl
# Modeltime Table
#Step 3: Calibrate model
calibration_tbl <- models_tbl %>%
modeltime_calibrate(new_data = testing(splits))
calibration_tbl
# Modeltime Table
modeltime_accuracy() function gives all accuracy metrics.
I like to pay attention to the the mean absolute percentage error (MAPE) which is 5.95 so our forecasts are off around 6%.
#Step 4: Get Accuracy Metrics
calibration_tbl %>%
modeltime_accuracy()
#Step 5: Create future forecast with confidence intervals
(forecast_tbl <- calibration_tbl %>%
modeltime_forecast(
new_data = testing(splits),
actual_data = postit,
keep_data = TRUE #Include the new data (and actual data) as extra columns with the results of the model forecasts
))
#Step 6: Plot modeltime forecast
plot_modeltime_forecast(forecast_tbl)
Forecast sales into the future
#Create a tibble of observations with length out being the number of observations we want in reference to our date variable (new_data). Since new_date ends on Dec 31st, our future_frame starts on Jan 1st counts 90 days into the future.
#Create tibble of dates using future_frame() from timetk package
dates <- postit %>%
future_frame(new_date, .length_out = "1 year")
#Simulate display data
display <- rep(0:1, each = 2, length.out = 365)
#Simulate Price data
Price <- rep(c(5.95, 6.1, 6.2, 6.98, 7.12, 7.32, 7.52), length.out = 365)
#Put data into dataframe
explanatory_data <- cbind(dates, display, Price)
#Add additional Month and day_num variables
explanatory_data <- explanatory_data %>%
mutate(Month = format(new_date, "%m"),
day_num = format(new_date, "%d"))
#Need to factor variables
fac_vars <- c("Month", "display", "Price")
#Factor Month, Display and Price Variables
explanatory_data[,fac_vars] <- lapply(explanatory_data[,fac_vars], factor)
#Change day_num from character to numeric vector and strip leading zeros
library(stringr)
explanatory_data$day_num <- as.numeric(str_remove(explanatory_data$day_num, "^0+"))
#Do the same for the Month variable
explanatory_data$Month <- as.factor( str_remove(explanatory_data$Month, "^0+"))
# explanatory_data$day_num <- as.numeric(explanatory_data$day_num)
#Check results
str(explanatory_data)
'data.frame': 365 obs. of 5 variables:
$ new_date: Date, format: "2014-01-01" ...
$ display : Factor w/ 2 levels "0","1": 1 1 2 2 1 1 2 2 1 1 ...
$ Price : Factor w/ 7 levels "5.95","6.1","6.2",..: 1 2 3 4 5 6 7 1 2 3 ...
$ Month : Factor w/ 12 levels "1","10","11",..: 1 1 1 1 1 1 1 1 1 1 ...
$ day_num : num 1 2 3 4 5 6 7 8 9 10 ...
CAUTION: It is super important that the data in your new data frame matches the exact same formatting of the data in the data used in building the forecast. If errors occur during either the model fit or forecasting phase, differently formatted data may be the culprit.
#Step 5 (Alternative): Specify the H or horizon to get a forecast into the future, but if using xregs (independent regressors), you must create a new dataframe with the xregs.
#forecast on the new tibble
forecast_tbl_future_data <- calibration_tbl %>%
modeltime_forecast(
new_data = explanatory_data
)
#plot forecast
plot_modeltime_forecast(forecast_tbl_future_data, .interactive = TRUE)
# Install ggpubr if needed and load library
if (!require("ggpubr")) install.packages("ggpubr")
library(ggpubr)
#Subset the date and prediction columns
final_fc <- forecast_tbl_future_data %>%
select(.index, .value) %>%
mutate(month_year = format(.index, "%m-%Y"))
#Group by month and sum sales
final_fc_gr <- final_fc %>%
group_by(month_year) %>%
summarize(sales_forecast = sum(.value))
#Put forecasts into a nice and pretty table
fc_ggtable <- final_fc_gr %>%
ggtexttable(theme = ttheme("mBlackWhite"), rows = NULL)
#Visualize table
fc_ggtable

FINAL THOUGHTS:
I really enjoyed performing time series analysis using Modeltime package. As a Tidyverse user, this way of working with machine learning models is streamlined, functional and flexible.
LS0tCnRpdGxlOiAiSG93IHRvIGZvcmVjYXN0IHByb2R1Y3Qgc2FsZXMgaW4gdGhlIFIgVGlkeXZlcnNlIHdpdGggTW9kZWx0aW1lIGFuZCBQcm9waGV0IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpEYXRhc2V0OgoKV2UgaGF2ZSAzIHllYXJzIG9mIGRhaWx5IHNhbGVzIG9mIFBvc3QtaXRzLiBUaG9zZSBsb3ZhYmxlIG5vdGUgc25pcHBldHMgdGhhdCBoYW5nIGFyb3VuZCB5b3VyIGRlc2suCgpXZSB3aWxsIGRvIHRoZSBmb2xsb3dpbmc6CgoxLiBEZXRlcm1pbmUgd2hpY2ggZmFjdG9ycyBpbmZsdWVuY2Ugc2FsZXMuCgoyLiBCdWlsZCBhIG1vZGVsIHRvIGZvcmVjYXN0IGRhaWx5IHNhbGVzLgoKCgpgYGB7ciBMb2FkIHBhY2thZ2VzfQojIEluc3RhbGwgcGFjbWFuIGlmIG5lZWRlZAppZiAoIXJlcXVpcmUoInBhY21hbiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKQoKIyBsb2FkIHBhY2thZ2VzCnBhY21hbjo6cF9sb2FkKHBhY21hbiwKICB0aWR5dmVyc2UsIG9wZW54bHN4LCBtb2RlbHRpbWUsIHBhcnNuaXAsIHJzYW1wbGUsIHRpbWV0aywgYnJvb20pCgpgYGAKCgoKYGBge3IgSW1wb3J0IGRhaWx5IHNhbGVzIGRhdGF9CiNJbXBvcnQgZGF0YQpwb3N0aXQgPC0gcmVhZC54bHN4KCIvVXNlcnMvYW5pdGFvd2Vucy9Eb2N1bWVudHMvTWFya2V0aW5nIEFuYWx5dGljcy1EYXRhIERyaXZlbiBUZWNobmlxdWVzIHdpdGggRXhjZWwvRXhjZWwgRmlsZXMvQ2hhcHRlciAzMSBFeGNlbCBGaWxlcy9QT1NUSVREQVRBLnhsc3giLCBza2lwRW1wdHlSb3dzID0gVFJVRSkKCiNDaGVjayByZXN1bHRzCnN0cihwb3N0aXQpCmBgYAoKV2UgaGF2ZSA2IHZhcmlhYmxlczoKCjEuIE1vbnRoCjIuIERheSBudW1iZXIKMy4gRGF5IChUaGlzIGlzIG91ciBkYXRlIHZhcmlhYmxlIGJ1dCBzaW5jZSB3ZSBpbXBvcnRlZCBmcm9tIEV4Y2VsIHdlIGdldCB0aGUgRXhjZWwgZm9ybWF0dGVkIGRhdGVzKQo0LiBQcmljaW5nIGZvciB0aGF0IHBhcnRpY3VsYXIgZGF5ICg3IGRpZmZlcmVudCBwcmljZXMpCjUuIERpc3BsYXkgKFRoaXMgaXMgYSBiaW5hcnkgdmFyaWFibGUuIDEgaW5kaWNhdGVzIHRoYXQgb3VyIHByb2R1Y3Qgd2FzIG9uIGEgZGlzcGxheSBmb3IgdGhhdCBkYXkuIDAgaW5kaWNhdGVzIHRoYXQgaXQgd2FzIG5vdCBvbiBkaXNwbGF5KS4KNi4gQWN0dWFsIHNhbGVzCgoKYGBge3IgQ3JlYXRlIE5ldyBGb3JtYXR0ZWQgRGF0ZSBDb2x1bW59CiNDcmVhdGUgTmV3IEZvcm1hdHRlZCBEYXRlIENvbHVtbgpwb3N0aXQkbmV3X2RhdGUgPC0gYXMuRGF0ZShwb3N0aXQkRGF5LCBvcmlnaW4gPSAiMTg5OS0xMi0zMCIpCgojQ2hlY2sgcmVzdWx0cwpzdHIocG9zdGl0JG5ld19kYXRlKQpgYGAKCgoKYGBge3IgUmVuYW1lIGNvbHVtbnMgc28gdGhhdCBpdCBkb2VzIG5vdCBjYXVzZSBwcm9ibGVtcyBsYXRlcn0KI0NvbHVtbiByZW5hbWluZwpwb3N0aXQgPC0gcG9zdGl0ICU+JSAKICByZW5hbWUoCiAgICBkYXlfbnVtID0gJ0RheSMnLAogICAgZGlzcGxheSA9ICdEaXNwbGF5PycKICApCgojQ2hlY2sgcmVzdWx0cwpuYW1lcyhwb3N0aXQpCmBgYAoKCmBgYHtyIEZhY3RvciBudW1lcmljIHZhcmlhYmxlc30KI0NyZWF0ZSBsaXN0IG9mIHZhcmlhYmxlcyB0aGF0IG5lZWQgdHJhbnNmb3JtYXRpb24KZmFjX3ZhcnMgPC0gYygiTW9udGgiLCAiZGlzcGxheSIsICJQcmljZSIpCgojRmFjdG9yIE1vbnRoLCBEaXNwbGF5IGFuZCBQcmljZSBWYXJpYWJsZXMKcG9zdGl0WyxmYWNfdmFyc10gPC0gbGFwcGx5KHBvc3RpdFssZmFjX3ZhcnNdLCBmYWN0b3IpIAoKI0NoZWNrIHJlc3VsdHMKc3RyKHBvc3RpdCkKYGBgCgpgYGB7ciBMZXQgdXMgZHJvcCB0aGUgRGF5IHZhcmlhYmxlIGFzIHdlIG5vIGxvbmdlciBuZWVkIGl0fQojRHJvcCBFeGNlbCBmb3JtYXR0ZWQgRGF5IHZhcmlhYmxlCnBvc3RpdCA8LSBwb3N0aXQgJT4lIAogICAgICAgICAgICBzZWxlY3QoLURheSkKYGBgCgoKCmBgYHtyIFZpc3VhbGl6ZSBzYWxlcyBvdmVyIHRpbWV9CiNGcm9tIHRpbWV0ayBwYWNrYWdlIC0gdmlzdWFsaXplIHNhbGVzCnBvc3RpdCAlPiUKICBwbG90X3RpbWVfc2VyaWVzKG5ld19kYXRlLCBhY3R1YWxzYWxlcywgLmludGVyYWN0aXZlID0gVFJVRSkKYGBgCgpPdXIgcGxvdCBvZiBzYWxlcyBpbmRpY2F0ZXMgdGhhdCB0aGVyZSBpcyBhIHBvc2l0aXZlIHRyZW5kIG9mIHNhbGVzIGZvciBvdXIgcG9zdCBpdHMuCgpUcmVuZCBpbiB0aW1lIHNlcmllcyBkYXRhIGlzIGEgc3BlY2lmaWMgcGF0dGVybiB3aGljaCBpbiB3aGljaCBvYnNlcnZhdGlvbnMgYXJlIGVpdGhlciBpbmNyZWFzaW5nIG9yIGRlY3JlYXNpbmcgb3ZlciB0aW1lLiBUcmVuZHMgY2FuIGNvbnN0YW50bHkgY2hhbmdlLgoKCgojIyBUYXNrIDE6IFdoYXQgZmFjdG9ycyBpbmZsdWVuY2Ugc2FsZXMKCgpgYGB7ciBEYXRhIHZpc3VhbGl6YXRpb259CiNMZXQncyBkbyBzb21lIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMgKEVEQSkKZ2dwbG90KGRhdGEgPSBwb3N0aXQsIGFlcyh4PWFjdHVhbHNhbGVzKSkgKyBnZW9tX2hpc3RvZ3JhbSgpCiNTYWxlcyBhcmUgYSBiaXQgc2tld2VkIHRvIHRoZSByaWdodAoKZ2dwbG90KGRhdGE9cG9zdGl0LCBhZXMoeD1sb2coYWN0dWFsc2FsZXMpKSkgKyBnZW9tX2hpc3RvZ3JhbSgpCiNTYWxlcyBub3cgbG9vayBtb3JlIG5vcm1hbCBhZnRlciBsb2cgdHJhbnNmb3JtYXRpb24KCmdncGxvdChkYXRhID0gcG9zdGl0LCBhZXMoeD1Nb250aCwgeSA9IGFjdHVhbHNhbGVzKSkgKyBnZW9tX2JveHBsb3QoKQojU2FsZXMgYXJlIGxvd2VzdCBpbiBNYXJjaC4gU2FsZXMgYXJlIGdlbmVyYWxseSBoaWdoZXIgaW4gUTQgYW5kIHRoZSBoaWdoZXN0IGluIERlY2VtYmVyLiBEZWNlbWJlciBhbHNvIGFwcGVhcnMgdG8gaGF2ZSB0aGUgbGFyZ2VzdCBzcHJlYWQuIFNvbWUgb3V0bGllcnMgZHVyaW5nIG1vbnRocyBBdWd1c3QgYW5kIFNlcHRlbWJlci4KCmdncGxvdChkYXRhID0gcG9zdGl0LCBhZXMoeD1QcmljZSwgeSA9IGFjdHVhbHNhbGVzKSkgKyBnZW9tX2JveHBsb3QoKQojVGhpcyBpcyBxdWl0ZSBpbnRlcmVzdGluZywgYXMgc2FsZXMgYXJlIGhpZ2hlc3Qgd2hlbiB0aGUgcHJpY2UgaXMgNS45NS4gU2FsZXMgZGVjcmVhc2Ugd2hlbiBwcmljZSBpbmNyZWFzZXMgdG8gNi4xIGFuZCBzbyBhbmQgc28gZm9ydGggd2hlcmUgdGhlIGxvd2VzdCBzYWxlcyBoYXBwZW4gd2hlbiB0aGUgcHJpY2UgaXMgYXQgaXRzIGhpZ2hlc3QgNy41Mi4gVGhlcmUgYXBwZWFycyB0byBiZSBhIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHByaWNlIGFuZCBzYWxlcy4KCmdncGxvdChkYXRhID0gcG9zdGl0LCBhZXMoeD1kaXNwbGF5LCB5ID0gYWN0dWFsc2FsZXMpKSArIGdlb21fYm94cGxvdCgpCiNNb3JlIHNhbGVzIHdoZW4gcHJvZHVjdCBpcyBvbiBkaXNwbGF5LiAKCnBvc3RpdCAlPiUgIAogIGdyb3VwX2J5KGRpc3BsYXkpICU+JSAKICBzdW1tYXJpemUobWVhbl9zYWxlcyA9IG1lYW4oYWN0dWFsc2FsZXMpLCBtZWRpYW5fc2FsZXMgPSBtZWRpYW4oYWN0dWFsc2FsZXMpLCBzZF9zYWxlcyA9IHNkKGFjdHVhbHNhbGVzKSkKI0F2ZXJhZ2Ugc2FsZXMgYXJlIDYzOSB2cyA1ODcgd2hlbiBubyBkaXNwbGF5LgpgYGAKTm93IHdlIGhhdmUgc29tZSBpZGVhIG9mIHRoZSBmYWN0b3JzIHRoYXQgbWlnaHQgaW5mbHVlbmNlIHNhbGVzLCBsZXQncyBjaGVjayBmb3Igc2lnbmlmaWNhbmNlIGJ5IHVzaW5nIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwuCgpgYGB7ciBNb2RlbCBzcGVjIGZvciBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbH0KIyBNb2RlbCBTcGVjCm1vZGVsX3NwZWNfbG0gPC0gbGluZWFyX3JlZygpICU+JQogICAgc2V0X2VuZ2luZSgibG0iKQpgYGAKCgpgYGB7ciBMaW5lYXIgUmVncmVzc2lvbiBNb2RlbCBGaXR9CiMgRml0IExpbmVhciBSZWdyZXNzaW9uIE1vZGVsCm1vZGVsX2ZpdF9sbSA8LSBtb2RlbF9zcGVjX2xtICU+JQogICAgZml0KGFjdHVhbHNhbGVzIH4gTW9udGggKyBQcmljZSArIGRpc3BsYXksIGRhdGEgPSBwb3N0aXQpCmBgYAoKYGBge3IgUHJpbnQgc3VtbWFyeSBvZiBtb2RlbCBpbiBhIHRpZHkgb2JqZWN0fQojUHJpbnQgc3VtbWFyeSBvZiBtb2RlbCBpbiBhIHRpZHkgb2JqZWN0CmxtX3N1bW1hcnkgPC0gdGlkeShtb2RlbF9maXRfbG0pICU+JSAKICAgICAgICAgICAgICBtdXRhdGUoc2lnbmlmaWNhbnQgPSBwLnZhbHVlIDw9IDAuMDUpCmxtX3N1bW1hcnkKI1VzZSB0aGUgZ2xhbmNlIGZ1bmN0aW9uIGZyb20gdGhlIGJyb29tIHBhY2thZ2UgdG8gZ2V0IGFkZGl0aW9uYWwgaW5mb3JtYXRpb24gZnJvbSB0aGUgbW9kZWwgKGUuZy4gbW9kZWwgc3RhdGl0aWNzIGxpa2Ugci5zcXVhcmVkKQpnbGFuY2UobW9kZWxfZml0X2xtKQpgYGAKQSBnZW5lcmljIGludGVycHJldGF0aW9uIG9mIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwuIEZvciBldmVyeSBvbmUgdW5pdCBjaGFuZ2UgaW4gY29lZmZpY2llbnQgKHRlcm0gY29sdW1uKSwgbW92ZXMgdW5pdCBzYWxlcyBieSB0aGUgdmFsdWUgb2Ygb3VyIGVzdGltYXRlIHdoaWxlIGFsbCBvdGhlciB2YXJpYWJsZXMgcmVtYWluIGF0IHRoZSBzYW1lIGxldmVsLgoKTW9zdCBtb250aHMgYXJlIHNpZ25pZmljYW50IGJ1dCBEZWNlbWJlciBpcyBwb3NpdGl2ZSBhbmQgdGhlIG1vc3Qgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCB3aXRoIHRoZSBoaWdoZXN0IGF2ZXJhZ2Ugc2FsZXMuIE1hcmNoIGlzIHRoZSBtb250aCB3aXRoIHRoZSBsb3dlc3QgYXZlcmFnZSBzYWxlcy4gVGltZSBvZiB5ZWFyIGRvZXMgaW1wYWN0IHNhbGVzLgoKQWxsIHByaWNlIGNvZWZmaWNpZW50cyBhcmUgbmVnYXRpdmUgYW5kIHNpZ25pZmljYW50IHdoaWNoIG1lYW5zIHRoYXQgYWxsIHByaWNlIGxldmVscyBhYm92ZSA1Ljk1IHJlZHVjZXMgc2FsZXMgYW5kIGNvbnRpbnVlcyB0byByZWR1Y2UgYXQgZWFjaCBwcmljZSBpbmNyZWFzZS4gQ3VzdG9tZXJzIHNlZW0gdG8gYmUgcHJpY2Ugc2Vuc2l0aXZlIHdoZW4gaXQgY29tZXMgdG8gcG9zdC1pdHMuIEFzIHN1Z2dlc3RlZCBieSBvdXIgRURBLCB0aGVyZSBpcyBhIG5lZ2F0aXZlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHByaWNlIGFuZCBzYWxlcy4KCkZvciBkaXNwbGF5IGFkdmVydGlzaW5nLCB3ZSBjYW4gZXhwZWN0IGFuIGluY3JlYXNlIG9mIDYwIHdoZW4gdGhlcmUgaXMgYSBkaXNwbGF5LgoKQXMgZm9yIHRoZSBtb2RlbCBzdGF0aXN0aWNzLCB3ZSBoYXZlIGEgdmVyeSBsb3cgcC12YWx1ZSBhbmQgYSBwcmV0dHkgZGVjZW50IHIuc3F1YXJlIG9mIC44ODUsIHdoaWNoIHRlbGxzIHVzIHRoYXQgdGhhdCA4OSUgb2YKdGhlIHZhcmlhdGlvbiBpbiBzYWxlcyBpcyBleHBsYWluZWQgYnkgb3VyIGluZGVwZW5kZW50L2V4cGxhbmF0b3J5IHZhcmlhYmxlcy4KCldlIGJ1aWx0IHRoaXMgbGluZWFyIG1vZGVsIGZvciBtb3N0bHkgZ2FpbmluZyBpbnNpZ2h0cyBpbnRvIHdoYXQgZmFjdG9ycyBpbmZsdWVuY2Ugc2FsZXMuCgojIyBUYXNrIDI6IEJ1aWxkIGEgZm9yZWNhc3QgbW9kZWwgZm9yIHNhbGVzIAoKV2Ugd2lsbCBzcGxpdCBvdXIgZGF0YXNldCBpbnRvIHRlc3QgYW5kIHRyYWluaW5nIGRhdGEuIFdlIHdpbGwgdXNlIHRoZSBsYXN0IDMgbW9udGhzIG9mIHRoZSBkYXRhc2V0IGFzIHRoZSB0cmFpbmluZyBzZXQuCgpgYGB7ciBUaW1lIHNlcmllcyBzcGxpdH0KI1NwbGl0IGRhdGEgaW50byB0ZXN0IGFuZCB0cmFpbmluZyBzZXQKc3BsaXRzIDwtIHBvc3RpdCAlPiUKICB0aW1lX3Nlcmllc19zcGxpdChkYXRlX3ZhciA9IG5ld19kYXRlLCBhc3Nlc3MgPSAiMTIgbW9udGhzIiwgY3VtdWxhdGl2ZSA9IFRSVUUpCmBgYAoKCmBgYHtyIFZpc3VhbGl6ZSB0ZXN0IHRyYWluIHNwbGl0fQojVmlzdWFsaXplIHRlc3QgdHJhaW4gc3BsaXQKc3BsaXRzICU+JQogIHRrX3RpbWVfc2VyaWVzX2N2X3BsYW4oKSAlPiUKICBwbG90X3RpbWVfc2VyaWVzX2N2X3BsYW4obmV3X2RhdGUsIGFjdHVhbHNhbGVzLCAuaW50ZXJhY3RpdmUgPSBUUlVFKQpgYGAKV2Ugd2FudCB0byBkZWZpbmUgb3VyIG1vZGVsIGVuZ2luZS4gV2Ugd2lsbCBiZSB1c2luZyBwcm9waGV0LgoKYGBge3IgTW9kZWwgU3BlY2lmaWNhdGlvbiAtIFRpbWUgU2VyaWVzIE1vZGVsfQojIE1vZGVsIFNwZWMKbW9kZWxfc3BlY19wcm9waGV0IDwtIHByb3BoZXRfcmVnKCkgJT4lCiAgICBzZXRfZW5naW5lKCJwcm9waGV0IikKYGBgCgoKCgpgYGB7ciBTdGVwIDE6IE1vZGVsIEZpdH0KIyBTdGVwIDE6IE1vZGVsIGZpdAptb2RlbF9maXRfcHJvcGhldCA8LSBtb2RlbF9zcGVjX3Byb3BoZXQgJT4lCiAgICBmaXQoYWN0dWFsc2FsZXMgfiAuLCBkYXRhID0gdHJhaW5pbmcoc3BsaXRzKSkKCm1vZGVsX2ZpdF9wcm9waGV0CmBgYAoKYGBge3IgU3RlcCAyOiBQdXQgbW9kZWwgb3V0cHV0IGludG8gYSBtb2RlbHRpbWUgdGFibGV9CiNTdGVwIDI6IFB1dCBtb2RlbCBpbnRvIGEgbW9kZWx0aW1lIHRhYmxlCm1vZGVsc190YmwgPC0gbW9kZWx0aW1lX3RhYmxlKG1vZGVsX2ZpdF9wcm9waGV0KQoKbW9kZWxzX3RibApgYGAKCgpgYGB7ciBTdGVwIDM6IENhbGlicmF0ZSBNb2RlbH0KI1N0ZXAgMzogQ2FsaWJyYXRlIG1vZGVsCmNhbGlicmF0aW9uX3RibCA8LSBtb2RlbHNfdGJsICU+JSAKICBtb2RlbHRpbWVfY2FsaWJyYXRlKG5ld19kYXRhID0gdGVzdGluZyhzcGxpdHMpKQoKY2FsaWJyYXRpb25fdGJsCmBgYAptb2RlbHRpbWVfYWNjdXJhY3koKSBmdW5jdGlvbiBnaXZlcyBhbGwgYWNjdXJhY3kgbWV0cmljcy4gCgpJIGxpa2UgdG8gcGF5IGF0dGVudGlvbiB0byB0aGUgdGhlIG1lYW4gYWJzb2x1dGUgcGVyY2VudGFnZSBlcnJvciAoTUFQRSkgd2hpY2ggaXMgNS45NSBzbyBvdXIgZm9yZWNhc3RzIGFyZSBvZmYgYXJvdW5kIDYlLiAKCmBgYHtyIFN0ZXAgNDogR2V0IGFjY3VyYWN5IG1ldHJpY3N9CiNTdGVwIDQ6IEdldCBBY2N1cmFjeSBNZXRyaWNzCmNhbGlicmF0aW9uX3RibCAlPiUKICAgIG1vZGVsdGltZV9hY2N1cmFjeSgpCmBgYAoKCgpgYGB7ciBTdGVwIDU6IENyZWF0ZSBmdXR1cmUgZm9yZWNhc3Qgb24gdGVzdCBzZXR9CiNTdGVwIDU6IENyZWF0ZSBmdXR1cmUgZm9yZWNhc3Qgd2l0aCBjb25maWRlbmNlIGludGVydmFscwooZm9yZWNhc3RfdGJsIDwtIGNhbGlicmF0aW9uX3RibCAlPiUKICAgIG1vZGVsdGltZV9mb3JlY2FzdCgKICAgICAgICBuZXdfZGF0YSAgICA9IHRlc3Rpbmcoc3BsaXRzKSwKICAgICAgICBhY3R1YWxfZGF0YSA9IHBvc3RpdCwKICAgICAgICBrZWVwX2RhdGEgPSBUUlVFICNJbmNsdWRlIHRoZSBuZXcgZGF0YSAoYW5kIGFjdHVhbCBkYXRhKSBhcyBleHRyYSBjb2x1bW5zIHdpdGggdGhlIHJlc3VsdHMgb2YgdGhlIG1vZGVsIGZvcmVjYXN0cwogICAgKSkKYGBgCgoKCgpgYGB7ciBTdGVwIDY6IFBsb3QgbW9kZWx0aW1lIGZvcmVjYXN0fQojU3RlcCA2OiBQbG90IG1vZGVsdGltZSBmb3JlY2FzdApwbG90X21vZGVsdGltZV9mb3JlY2FzdChmb3JlY2FzdF90YmwpCmBgYAoKCiMjIEZvcmVjYXN0IHNhbGVzIGludG8gdGhlIGZ1dHVyZQpgYGB7ciBDcmVhdGUgbmV3IHRpYmJsZSBkYXRhZnJhbWUgdGhhdCB3aWxsIGJlIHVzZWQgdG8gcHJlZGljdCBmdXR1cmUgdmFsdWVzfQojQ3JlYXRlIGEgdGliYmxlIG9mIG9ic2VydmF0aW9ucyB3aXRoIGxlbmd0aCBvdXQgYmVpbmcgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgd2Ugd2FudCBpbiByZWZlcmVuY2UgdG8gb3VyIGRhdGUgdmFyaWFibGUgKG5ld19kYXRhKS4gU2luY2UgbmV3X2RhdGUgZW5kcyBvbiBEZWMgMzFzdCwgb3VyIGZ1dHVyZV9mcmFtZSBzdGFydHMgb24gSmFuIDFzdCBjb3VudHMgOTAgZGF5cyBpbnRvIHRoZSBmdXR1cmUuCgojQ3JlYXRlIHRpYmJsZSBvZiBkYXRlcyB1c2luZyBmdXR1cmVfZnJhbWUoKSBmcm9tIHRpbWV0ayBwYWNrYWdlCmRhdGVzIDwtIHBvc3RpdCAlPiUgCiAgZnV0dXJlX2ZyYW1lKG5ld19kYXRlLCAubGVuZ3RoX291dCA9ICIxIHllYXIiKQoKI1NpbXVsYXRlIGRpc3BsYXkgZGF0YQpkaXNwbGF5IDwtIHJlcCgwOjEsIGVhY2ggPSAyLCBsZW5ndGgub3V0ID0gMzY1KQoKI1NpbXVsYXRlIFByaWNlIGRhdGEKUHJpY2UgPC0gcmVwKGMoNS45NSwgNi4xLCA2LjIsIDYuOTgsIDcuMTIsIDcuMzIsIDcuNTIpLCBsZW5ndGgub3V0ID0gMzY1KQoKI1B1dCBkYXRhIGludG8gZGF0YWZyYW1lCmV4cGxhbmF0b3J5X2RhdGEgPC0gY2JpbmQoZGF0ZXMsIGRpc3BsYXksIFByaWNlKQoKI0FkZCBhZGRpdGlvbmFsIE1vbnRoIGFuZCBkYXlfbnVtIHZhcmlhYmxlcwpleHBsYW5hdG9yeV9kYXRhIDwtIGV4cGxhbmF0b3J5X2RhdGEgJT4lIAogIG11dGF0ZShNb250aCA9IGZvcm1hdChuZXdfZGF0ZSwgIiVtIiksCiAgICAgICAgIGRheV9udW0gPSBmb3JtYXQobmV3X2RhdGUsICIlZCIpKQoKI05lZWQgdG8gZmFjdG9yIHZhcmlhYmxlcyAKZmFjX3ZhcnMgPC0gYygiTW9udGgiLCAiZGlzcGxheSIsICJQcmljZSIpCgojRmFjdG9yIE1vbnRoLCBEaXNwbGF5IGFuZCBQcmljZSBWYXJpYWJsZXMKZXhwbGFuYXRvcnlfZGF0YVssZmFjX3ZhcnNdIDwtIGxhcHBseShleHBsYW5hdG9yeV9kYXRhWyxmYWNfdmFyc10sIGZhY3RvcikgCgojQ2hhbmdlIGRheV9udW0gZnJvbSBjaGFyYWN0ZXIgdG8gbnVtZXJpYyB2ZWN0b3IgYW5kIHN0cmlwIGxlYWRpbmcgemVyb3MKbGlicmFyeShzdHJpbmdyKQpleHBsYW5hdG9yeV9kYXRhJGRheV9udW0gPC0gYXMubnVtZXJpYyhzdHJfcmVtb3ZlKGV4cGxhbmF0b3J5X2RhdGEkZGF5X251bSwgIl4wKyIpKSAKCiNEbyB0aGUgc2FtZSBmb3IgdGhlIE1vbnRoIHZhcmlhYmxlCmV4cGxhbmF0b3J5X2RhdGEkTW9udGggPC0gYXMuZmFjdG9yKCBzdHJfcmVtb3ZlKGV4cGxhbmF0b3J5X2RhdGEkTW9udGgsICJeMCsiKSkKCiMgZXhwbGFuYXRvcnlfZGF0YSRkYXlfbnVtIDwtIGFzLm51bWVyaWMoZXhwbGFuYXRvcnlfZGF0YSRkYXlfbnVtKQoKI0NoZWNrIHJlc3VsdHMKc3RyKGV4cGxhbmF0b3J5X2RhdGEpCmBgYAoKQ0FVVElPTjogSXQgaXMgc3VwZXIgaW1wb3J0YW50IHRoYXQgdGhlIGRhdGEgaW4geW91ciBuZXcgZGF0YSBmcmFtZSBtYXRjaGVzIHRoZSBleGFjdCBzYW1lIGZvcm1hdHRpbmcgb2YgdGhlIGRhdGEgaW4gdGhlIGRhdGEgdXNlZCBpbiBidWlsZGluZyB0aGUgZm9yZWNhc3QuIElmIGVycm9ycyBvY2N1ciBkdXJpbmcgZWl0aGVyIHRoZSBtb2RlbCBmaXQgb3IgZm9yZWNhc3RpbmcgcGhhc2UsIGRpZmZlcmVudGx5IGZvcm1hdHRlZCBkYXRhIG1heSBiZSB0aGUgY3VscHJpdC4KCgoKYGBge3IgU3RlcCA1IEFMVEVSTkFUSVZFOiBDcmVhdGUgZnV0dXJlIGZvcmVjYXN0IG9uIG5ldyBkYXRlc30KI1N0ZXAgNSAoQWx0ZXJuYXRpdmUpOiBTcGVjaWZ5IHRoZSBIIG9yIGhvcml6b24gdG8gZ2V0IGEgZm9yZWNhc3QgaW50byB0aGUgZnV0dXJlLCBidXQgaWYgdXNpbmcgeHJlZ3MgKGluZGVwZW5kZW50IHJlZ3Jlc3NvcnMpLCB5b3UgbXVzdCBjcmVhdGUgYSBuZXcgZGF0YWZyYW1lIHdpdGggdGhlIHhyZWdzLgoKI2ZvcmVjYXN0IG9uIHRoZSBuZXcgdGliYmxlCmZvcmVjYXN0X3RibF9mdXR1cmVfZGF0YSA8LSBjYWxpYnJhdGlvbl90YmwgJT4lCiAgICBtb2RlbHRpbWVfZm9yZWNhc3QoCiAgICAgICAgbmV3X2RhdGEgICAgPSBleHBsYW5hdG9yeV9kYXRhCiAgICApCgojcGxvdCBmb3JlY2FzdApwbG90X21vZGVsdGltZV9mb3JlY2FzdChmb3JlY2FzdF90YmxfZnV0dXJlX2RhdGEsIC5pbnRlcmFjdGl2ZSA9IFRSVUUpCmBgYAoKCmBgYHtyIFB1dCBmb3JlY2FzdHMgaW4gYSBwcmV0dHkgY2hhcnR9CiMgSW5zdGFsbCBnZ3B1YnIgaWYgbmVlZGVkIGFuZCBsb2FkIGxpYnJhcnkKaWYgKCFyZXF1aXJlKCJnZ3B1YnIiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dwdWJyIikKbGlicmFyeShnZ3B1YnIpCgojU3Vic2V0IHRoZSBkYXRlIGFuZCBwcmVkaWN0aW9uIGNvbHVtbnMKZmluYWxfZmMgPC0gZm9yZWNhc3RfdGJsX2Z1dHVyZV9kYXRhICU+JQogIHNlbGVjdCguaW5kZXgsIC52YWx1ZSkgJT4lIAogIG11dGF0ZShtb250aF95ZWFyID0gZm9ybWF0KC5pbmRleCwgIiVtLSVZIikpCgojR3JvdXAgYnkgbW9udGggYW5kIHN1bSBzYWxlcwpmaW5hbF9mY19nciA8LSBmaW5hbF9mYyAlPiUgCiAgZ3JvdXBfYnkobW9udGhfeWVhcikgJT4lIAogIHN1bW1hcml6ZShzYWxlc19mb3JlY2FzdCA9IHN1bSgudmFsdWUpKSAKCiNQdXQgZm9yZWNhc3RzIGludG8gYSBuaWNlIGFuZCBwcmV0dHkgdGFibGUKZmNfZ2d0YWJsZSA8LSBmaW5hbF9mY19nciAlPiUgCiAgZ2d0ZXh0dGFibGUodGhlbWUgPSB0dGhlbWUoIm1CbGFja1doaXRlIiksIHJvd3MgPSBOVUxMKQoKI1Zpc3VhbGl6ZSB0YWJsZQpmY19nZ3RhYmxlCmBgYAoKCgoKCgpGSU5BTCBUSE9VR0hUUzoKCkkgcmVhbGx5IGVuam95ZWQgcGVyZm9ybWluZyB0aW1lIHNlcmllcyBhbmFseXNpcyB1c2luZyBNb2RlbHRpbWUgcGFja2FnZS4gQXMgYSBUaWR5dmVyc2UgdXNlciwgdGhpcyB3YXkgb2Ygd29ya2luZyB3aXRoIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIGlzIHN0cmVhbWxpbmVkLCBmdW5jdGlvbmFsIGFuZCBmbGV4aWJsZS4=